use tiberius::error::Error;
use tiberius::{Client, Row};
use tokio::net::TcpStream;
use tokio_util::compat::{Compat, TokioAsyncWriteCompatExt};

use crate::{Info, SqlConfig};

const QUERY: &str = r#"
         select tab.TABLE_NAME,COLUMN_NAME,ORDINAL_POSITION,DATA_TYPE,IS_NULLABLE
         from INFORMATION_SCHEMA.TABLES tab
         inner join INFORMATION_SCHEMA.COLUMNS col 
         on tab.TABLE_NAME = col.TABLE_NAME
         where TABLE_TYPE = 'base table'
        "#;

/// 从SQL配置中异步获取数据库信息
///
/// 将给定的`SqlConfig`配置转换为MSSQL连接配置，并执行SQL查询以获取数据库表的结构信息。
///
/// # 参数
///
/// - `config`: 指向`SqlConfig`结构体的引用，包含连接数据库所需的配置信息。
///
/// # 返回值
///
/// - `Result<Vec<Info>, Error>`：包含`Info`结构体向量的`Result`枚举，表示数据库表的结构信息；如果发生错误，则返回`Error`。
pub(crate) async fn fetch_data(config: &SqlConfig) -> Result<Vec<Info>, Error> {
    let mut client = build_client(config).await?;
    let stream = client.simple_query(QUERY).await?;
    let list = stream
        .into_results()
        .await?
        .into_iter()
        .flat_map(|v| v.iter().map(to_info).collect::<Vec<_>>())
        .collect::<Vec<_>>();
    Ok(list)
}

/// 异步构建一个连接到数据库的客户端
///
/// # 参数
/// - `config`: SQL配置信息
///
/// # 返回值
/// - `Result<Client<Compat<TcpStream>>, Error>`: 成功时返回连接成功的客户端，失败时返回错误
async fn build_client(config: &SqlConfig) -> Result<Client<Compat<TcpStream>>, Error> {
    let config = config.to_mssql_config();
    let addr = config.get_addr();
    let tcp = TcpStream::connect(addr).await?;
    tcp.set_nodelay(true)?;
    let client = Client::connect(config, tcp.compat_write()).await?;
    Ok(client)
}

/// 将数据库查询结果行转换为Info结构体
///
/// # 参数
/// - `row`: 数据库查询结果行
///
/// # 返回值
/// - `Info`: 包含数据库表列信息的结构体
fn to_info(row: &Row) -> Info {
    let table_name: &str = row.get("TABLE_NAME").unwrap();
    let column_name: &str = row.get(1).unwrap();
    let ordinal_position: i32 = row.get(2).unwrap();
    let ordinal_position = ordinal_position as u64;
    let data_type: &str = row.get(3).unwrap();
    let is_nullable: &str = row.get(4).unwrap();
    Info {
        ordinal_position,
        table_name: table_name.to_string(),
        data_type: data_type.to_string(),
        is_nullable: is_nullable.to_string(),
        column_name: column_name.to_string(),
        column_comment: None,
    }
}
